package Client.Dropbox.SystemCommands

import java.nio.file.{StandardWatchEventKind, FileSystems, WatchKey}
import java.nio.file
import java.nio.file.Files
import java.nio.file.SimpleFileVisitor
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.FileVisitResult
import java.nio.file.FileVisitor
import java.nio.file.LinkOption
import Client.Dropbox.Manager.FileManager
import Client.Dropbox.Manager.FileCommand
import Client.Dropbox.DropboxMain
import objects._
import java.util.HashMap

/**
 * Object to listen to the operating system for changes in the
 * Dropbox folder
 */
object SystemListener extends Runnable{
  //watchService to watch the folder for updates
  var watcher=FileSystems.getDefault.newWatchService
  //path to the folder
  var root=FileSystems.getDefault.getPath("").asInstanceOf[Path]
  //hash map of keys and paths
  var map=new HashMap[WatchKey, Path]
  var stop=false
  var dead=null.asInstanceOf[Path]
  var nameChange=null.asInstanceOf[Path]
  var nameChangeID:Long=0
  
  /**
   * Class that registers each folder for watching
   */
  class sfv extends SimpleFileVisitor[Path]{
    /**
     * At each folder, register it for watching
     * @param dir the directory to watch
     * @param atts file attributes
     */
    override def preVisitDirectory(dir:Path, atts:BasicFileAttributes):FileVisitResult={
        register(dir)
        return FileVisitResult.CONTINUE
      }
  }
  
  /**
   * Register a folder for watching
   * @param dir the directory to watch
   */
  def register (dir:Path) {
    val key=dir.register(watcher, StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_DELETE, StandardWatchEventKind.ENTRY_MODIFY)
    map.put(key, dir)
  }
  
  /**
   * Choose the root directory
   * @param path the chosen root
   */
  def setRoot(path:String)={
    root=FileSystems.getDefault.getPath(path).asInstanceOf[Path]
  }
  
  /**
   * Set stop as true to exit loop
   */
  def stopRunning = stop=true
  
  /**
   * Run method for thread, to listen to the directory
   */
  def run = {
    try
    {
      Files walkFileTree(root, new sfv)
      while (!stop){
        //wait for change from folder and read it
        var key=watcher.take
        if (key.isInstanceOf[WatchKey]){
          var parent=map.get(key)
          //read events from list
          val events=key.asInstanceOf[WatchKey].pollEvents
          if (events.isEmpty)
            dead=parent
          while (!events.isEmpty){
            val event=events.remove(0)
            //get name of file that has changed and type of change
            val name=event.context.asInstanceOf[Path]
            val file=parent.resolve(name.asInstanceOf[Path])
            (event.kind match {
              case StandardWatchEventKind.ENTRY_CREATE => {
                if (file.isInstanceOf[Path])
                {
                  if(Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS))
                  {
                    //if a file's name was changed
                    if (nameChange!=null){
                      FileManager.add(root.relativize(file).toString, nameChangeID.asInstanceOf[Long])
                      nameChange=null
                    }
                    //if a new folder was created
                    else{
                      println("System: New Folder "+file)
                      file.asInstanceOf[Path].register(watcher, StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_DELETE, StandardWatchEventKind.ENTRY_MODIFY)
              	    DropboxMain.boxManager ! new FileCommand(file, SystemCommand.NewFolder) 
                    }
                  }
                  //if a new file was created
                  else if (Files.exists(file, LinkOption.NOFOLLOW_LINKS)){
                    println("System: New File "+file)
                    DropboxMain.boxManager ! new FileCommand(file, SystemCommand.NewFile)
                  }
                }
              }
              case StandardWatchEventKind.ENTRY_MODIFY => {
                //if a file was changed
                if(!Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)&&Files.exists(file, LinkOption.NOFOLLOW_LINKS)&&Files.isReadable(file)){
                  println("System: Changed File "+file)
                  DropboxMain.boxManager ! new FileCommand(file, SystemCommand.ChangedFile)
                }
              }
              case StandardWatchEventKind.ENTRY_DELETE => {
                if(dead==null){
                  var rel=root.relativize(file).toString
                  nameChangeID=FileManager.remove(rel)
                  //if a file has been deleted
                  if (nameChangeID==0){
                    println("System: Deleted File "+file)
                    DropboxMain.boxManager ! new FileCommand(file, SystemCommand.DeletedFile)
                  }
                }
                //if a folder has been deleted
                else if (dead.equals(file)){
                  println("System: Delete Folder "+file)
                  DropboxMain.boxManager ! new FileCommand(dead, SystemCommand.DeletedFolder)
                  dead=null.asInstanceOf[Path]
                }
              }
            })
          }
          var valid = key.reset()
          if (!valid){
            map.remove(key)
            if (map.isEmpty)
              stop=true
          }
        }
      }
    }
    catch {
      case (e:Exception) => System.err.println(e.getMessage)
    }
  }
}